/*===============================================
 *   文件名称：v4l2.c
 *   创 建 者: 麦穗飘飘
 *   创建日期：2024年12月12日   11:27:30
 *   描    述：视频设备操作函数
 ================================================*/
#include "v4l2.h"

/* 延时函数 */
void delay(int milliseconds) {
    struct timespec req, rem;
    req.tv_sec = milliseconds / 1000;
    req.tv_nsec = (milliseconds % 1000) * 1000000;
    while (nanosleep(&req, &rem) == -1) {
        req = rem;
    }
}

/* 打开视频设备 */
int open_video_device(const char *device_path) {
    int fd = open(device_path, O_RDWR);
    if (fd < 0) {
        perror("打开设备失败");
        return -1;
    }
    return fd;
}

/* 查询设备能力 */
int query_capabilities(int fd) {
    struct v4l2_capability cap;
    if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
        perror("VIDIOC_QUERYCAP failed");
        return -1;
    }
    if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
        printf("支持视频捕获\n");
    } else {
        fprintf(stderr, "设备不支持视频捕获\n");
        return -1;
    }
    return 0;
}

/* 列出支持的格式 */
int list_supported_formats(int fd) {
    struct v4l2_fmtdesc fmtdesc;
    fmtdesc.index = 0;
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == -1) {
        perror("VIDIOC_ENUM_FMT failed");
        return -1;
    }
    if (fmtdesc.pixelformat == V4L2_PIX_FMT_YUYV) {
        printf("支持YUYV\n");
    } else if (fmtdesc.pixelformat == V4L2_PIX_FMT_MJPEG) {
        printf("支持MJPEG\n");
    } else {
        fprintf(stderr, "不支持所需的图像格式\n");
        return -1;
    }
    return 0;
}

/* 设置视频格式 */
int set_video_format(int fd, int width, int height, int pixel_format) {
    struct v4l2_format vfmt;
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    vfmt.fmt.pix.width = width;
    vfmt.fmt.pix.height = height;
    vfmt.fmt.pix.pixelformat = pixel_format;
    int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);
    if (ret < 0) {
        perror("设置格式失败");
        return -1;
    }
    memset(&vfmt, 0, sizeof(vfmt));
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_G_FMT, &vfmt);
    if (ret < 0) {
        perror("获取格式失败");
        return -1;
    }
    printf("pix_width = %d, pix_height = %d\n", vfmt.fmt.pix.width, vfmt.fmt.pix.height);
    return 0;
}

/* 请求缓冲区 */
int request_buffers(int fd, int count) {
    struct v4l2_requestbuffers reqbuffer;
    reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuffer.count = count;
    reqbuffer.memory = V4L2_MEMORY_MMAP;
    int ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
    if (ret < 0) {
        perror("请求缓冲区失败");
        return -1;
    }
    return 0;
}

/* 映射缓冲区 */
int map_buffers(int fd, unsigned char **buffer, unsigned int *size) {
    struct v4l2_buffer mapbuffer;
    mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    mapbuffer.index = 0; // 只需要一个缓冲区

    // 查询缓冲区
    int ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);
    if (ret < 0) {
        perror("查询缓冲区失败");
        return -1;
    }

    // 映射缓冲区
    *buffer = (unsigned char *) mmap(NULL, mapbuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset);
    if (*buffer == MAP_FAILED) {
        perror("内存映射失败");
        return -1;
    }
    *size = mapbuffer.length;

    // 将缓冲区加入队列
    ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
    if (ret < 0) {
        perror("放回缓冲区失败");
        return -1;
    }

    return 0;
}

/* 启动视频流 */
int start_stream(int fd) {
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int ret = ioctl(fd, VIDIOC_STREAMON, &type);
    if (ret < 0) {
        perror("启动流失败");
        return -1;
    }
    return 0;
}

/* 读取视频帧 */
int read_frame(int fd, unsigned char *buffer, unsigned int size, FILE *file) {
    struct v4l2_buffer readbuffer;
    readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    readbuffer.memory = V4L2_MEMORY_MMAP;

    // 等待数据准备就绪
    int ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
    if (ret < 0) {
        perror("读取数据失败");
        return -1;
    }

    // 写入图像数据到文件
    fwrite(buffer, size, 1, file);

    // 将缓冲区重新放回队列
    ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
    if (ret < 0) {
        perror("放回队列失败");
        return -1;
    }

    return 0;
}

/* 停止视频流 */
int stop_stream(int fd) {
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    int ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
    if (ret < 0) {
        perror("停止流失败");
        return -1;
    }
    return 0;
}

/* 拍照 */
int take_picture(const char *device_path, const char *output_file_path, int width, int height, int pixel_format) {    // 设备路径，输出文件路径，图像宽度，图像高度，图像格式
    int fd = open_video_device(device_path);    // 打开设备
    if (fd < 0) {
        return -1;
    }

    if (query_capabilities(fd) < 0) {    // 查询设备能力
        close(fd);
        return -1;
    }

    if (list_supported_formats(fd) < 0) {    // 列出支持的格式
        close(fd);
        return -1;
    }

    if (set_video_format(fd, width, height, pixel_format) < 0) {    // 设置视频格式
        close(fd);
        return -1;
    }

    if (request_buffers(fd, BUFFER_COUNT) < 0) {    // 请求缓冲区
        close(fd);
        return -1;
    }

    unsigned char *buffer;  // 图像数据缓冲区
    unsigned int size;      // 图像数据大小
    if (map_buffers(fd, &buffer, &size) < 0) {    // 映射缓冲区
        close(fd);
        return -1;
    }

    if (start_stream(fd) < 0) {    // 启动视频流
        munmap(buffer, size);    // 解除映射
        close(fd);
        return -1;
    }

    FILE *file = fopen(output_file_path, "w+"); // 打开输出文件
    if (!file) {
        perror("打开文件失败");
        munmap(buffer, size);    // 解除映射
        close(fd);
        return -1;
    }

    if (read_frame(fd, buffer, size, file) < 0) {    // 读取视频帧
        fclose(file);
        munmap(buffer, size);
        close(fd);
        return -1;
    }

    fclose(file);
    if (stop_stream(fd) < 0) {
        munmap(buffer, size);
        close(fd);
        return -1;
    }

    munmap(buffer, size);
    close(fd);

    return 0;
}

